package bgai.gamelogic;


/**
 * Singola mossa corrispondente all'uso di un singolo dado per muovere una
 * pedina. Il movimento multiplo dello stesso checker e' rappresentato
 * dalla classe {@link CheckerMove}.
 * <p>
 * Non viene effettuato alcun tipo di controllo sulla legalita' di questa mossa!
 * Ad esempio un movimento 4/12 usando un solo dado e' sicuramente impossibile,
 * ma questo non viene verificato qui.
 * @see CheckerMove
 */
public class SingleMove implements Cloneable, Comparable<SingleMove> {

	/**
	 * Posizione di partenza
	 */
	private int startPos;
	/**
	 * Posizione di arrivo
	 */
	private int endPos;
	/**
	 * true se in corrispondenza di questa mossa e' stata mangiata una pedina
	 * avversaria
	 */
	private boolean hit;
	/**
	 * Dado utilizzato per questa mossa
	 */
	private int dice;
	/**
	 * Colore del checker
	 */
	CheckerColor color;
	
	/**
	 * Crea una singola mossa.
	 * <p>
	 * Verifica solo che le posizioni iniziali e finali siano legali e che il dado sia corretto
	 * @see Backgammon
	 * @param startPos posizione iniziale. {@link Move#BAR} se inizia dalla bar
	 * @param endPos posizione finale. {@link Move#OFF} in caso di bear off
	 * @param dice dado utilizzato per questa mossa
	 * @param color colore del checker da muovere
	 * @throws PositionException 
	 * @throws DiceException
	 */
	public SingleMove(int startPos, int endPos, int dice, CheckerColor color) throws PositionException, DiceException {
		super();

		if (!Backgammon.checkPosition(startPos) && !Backgammon.isOff(startPos)) {
			throw new PositionException(startPos, "Posizione di partenza non valida");
		}
		if (!Backgammon.checkPosition(endPos) && !Backgammon.isBar(endPos)) {
			throw new PositionException(endPos, "Posizione di arrivo non valida");
		}
		if (!Backgammon.checkDice(dice)) {
			throw new DiceException(dice, 0, "Dado non valido");
		}

		this.startPos = startPos;
		this.endPos = endPos;
		this.dice= dice;
		this.color= color;
		this.hit= false;
	}

	/**
	 * Modifica il flag di hit
	 * @param hit
	 */
	public void setHit(boolean hit) {
		this.hit= hit;
	}
	
	/**
	 * Restituisce la posizione iniziale della mossa
	 * @return un valore compreso in [1; 24] oppure {@link Backgammon#BAR} oppure {@link Backgammon#OFF}
	 */
	public int getStartingPosition() {
		return startPos;
	}

	/**
	 * Restituisce la posizione finale della mossa
	 * @return un valore compreso in [1; 24] oppure {@link Backgammon#BAR} oppure {@link Backgammon#OFF}
	 */
	public int getEndingPosition() {
		return endPos;
	}

	/**
	 * Restituisce true se e' stato mangiato un checker al termine di questa
	 * mossa
	 * @return
	 */
	public boolean isHit() {
		return hit;
	}

	/**
	 * Restituisce il dado usato per questa mossa
	 * @return
	 */
	public int getDice() {
		return dice;
	}
	
	/**
	 * Colore della pedina
	 */
	public CheckerColor getColor() {
		return color;
	}
	
	/**
	 * Restituisce true se questa mossa inizia dalla bar
	 * @return
	 */
	public boolean fromBar() {
		return Backgammon.isBar(startPos);
	}

	/**
	 * Restituisce true se questa mossa termina fuori dalla tavola (bear off)
	 * @return
	 */
	public boolean bearOff() {
		return Backgammon.isOff(endPos);
	}
	
	@Override
	public String toString() {
		String start, end, str;

		if (fromBar()) start= "bar";
		else start= Integer.toString(startPos);

		if (bearOff()) end= "off";
		else end= Integer.toString(endPos);

		str= start + "/" + end;

		if (isHit()) str += "*";

		return str; 
	}
	
	public String rawToString() {
		return "("+dice+")"+toString();
	}
	
	@Override
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof SingleMove) {
        	SingleMove sm = (SingleMove) anObject;
        	
        	if (this.hit != sm.hit) return false;
        	if (this.startPos != sm.startPos) return false;
        	if (this.endPos != sm.endPos) return false;
        	if (this.dice != sm.dice) return false;
        	
        	// tutto uguale
        	return true;
        }
        return false;
    }

	@Override
	public Object clone() {
		try {
			
			// solo tipi primitivi
			
			return super.clone();
			
		} catch (CloneNotSupportedException e) {
			// non accadra' mai
			throw new InternalError("Come puo' essere CloneNotSupportedException?");
		}
	}
	
	@Override
	public int hashCode() {
		int hash= 1;
		
		hash = 7 * hash + dice;
		hash = 17 * hash + startPos;
		hash = 61 * hash + endPos;
		hash = 31 * hash + (hit ? Boolean.TRUE.hashCode() : Boolean.FALSE.hashCode() );

		return hash;
	}

	/**
	 * Ordinamento totale!!!
	 */
	@Override
	public int compareTo(SingleMove o) {

		// guardo le posizioni

		// calcolo le distanze dal bear off
		int startDist1;
		int startDist2;
		int endDist1;
		int endDist2;

		if (Backgammon.isBar(this.getStartingPosition())) {
			startDist1= Backgammon.NUM_POINTS+1;
		} else {
			startDist1= Backgammon.bearOffDistance(this.getStartingPosition(), this.color);
		}
		if (Backgammon.isOff(this.getEndingPosition())) {
			endDist1= 0;
		} else {
			endDist1= Backgammon.bearOffDistance(this.getEndingPosition(), this.color);
		}

		if (Backgammon.isBar(o.getStartingPosition())) {
			startDist2= Backgammon.NUM_POINTS+1;
		} else {
			startDist2= Backgammon.bearOffDistance(o.getStartingPosition(), o.color);
		}
		if (Backgammon.isOff(o.getEndingPosition())) {
			endDist2= 0;
		} else {
			endDist2= Backgammon.bearOffDistance(o.getEndingPosition(), o.color);
		}

		// posizioni di partenza
		// ordino da quello piu' lontano dal bear off a quello piu' vicino
		if (startDist1 < startDist2) {
			return 1;
		} else if (startDist1 > startDist2) {
			return -1;
		} else {

			// posizioni di arrivo
			// ordino da quello piu' lontano dal bear off a quello piu' vicino
			
			if (endDist1 < endDist2) {
				return 1;
			} else if (endDist1 > endDist2) {
				return -1;
			} else {
				
				// stessa posizione di partenza e arrivo => guardo hit
				
				if (this.hit && o.hit) return 0;
				else if (this.hit) return -1;
				else if (o.hit) return 1;
				else return 0; // nessuno e' hit
				
			}

		}


	}
}